#include <stdio.h>
#include <dlfcn.h>
#include <libxml/xmlreader.h>
#include <libxml/xpath.h>
#include <sys/stat.h>
#include <string.h>
#include <glob.h>
#include <unistd.h>
#include "fscontroller.h"
#include "fuse_compats.h"

using namespace std;

FSController::FSController() {
	this->host_config = NULL;
}

/* Replaces *cur with loaded config */ 
xmlNodePtr FSController::loadSubconfig(xmlNodePtr cur, const char *src) {
				xmlDocPtr doc = loadConfig(src);
				xmlNodePtr root = xmlDocGetRootElement(doc);
				xmlNodePtr inc_root = root;
				if(!xmlStrcmp(inc_root->name, (const xmlChar*) "fusezilla_config"))
					inc_root = inc_root->xmlChildrenNode;
				xmlNodePtr last = inc_root;
				while(last->next && (last = last->next));
				last->next = cur->next;
				cur->prev->next = inc_root;
				return(cur);
}

/* Expands <include/> directives */
xmlNodePtr FSController::loadIncludes(xmlNodePtr root) {
	xmlNodePtr cur = root->xmlChildrenNode;
	do {
		if(!xmlStrcmp(cur->name, (const xmlChar*) "include")) {
			xmlChar *src;
			int i;
			if(src = xmlGetProp(cur, (const xmlChar*) "glob")) {
				glob_t files;
				glob((const char*) src, 0, NULL, &files);
				for(i = 0; i < files.gl_pathc; i++) {
					loadSubconfig(cur, files.gl_pathv[i]);
				}
				globfree(&files);
				xmlFree(src);
			} else if(src = xmlGetProp(cur, (const xmlChar*) "href")) {
				loadSubconfig(cur,(const char*) src);
				xmlFree(src);
			} else {
				// Error
			}
		} else if(cur->xmlChildrenNode) {
			loadIncludes(cur);
		}
	} while(cur = cur->next);
	return(root);
}

/* Loads global config from file */
xmlDocPtr FSController::loadConfig(const char *path_1) {
	char *old_path = getcwd(NULL, 4096);
	char *path;
	if(path_1) {
		path = strdup(path_1);
	} else {
		char *home = getenv("HOME");
		char cfg_dir[] = "/.fusezilla/";
		char cfg_fname[] = "fusezillarc.xml";
		path = (char*) malloc(strlen(home) + strlen(cfg_dir) + strlen(cfg_fname) + 1);
		strcpy(path, home);
		strcat(path, cfg_dir);
		mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
		chdir(path);
		strcat(path, cfg_fname);
	}
	xmlDocPtr result = xmlParseFile(path);
	xmlNodePtr root = xmlDocGetRootElement(result);
	loadIncludes(root);
	free(path);
	chdir(old_path);
	return(result);
}

/* Loads config for a partitial host */
xmlDocPtr FSController::loadHostConfig(const int key, const char* value) {
	xmlDocPtr result = loadConfig(NULL);
	xmlNodePtr cur = xmlDocGetRootElement(result);
	xmlXPathContextPtr context = xmlXPathNewContext(result);
	xmlXPathObjectPtr globalOpts = xmlXPathEvalExpression((const xmlChar*) "/fusezilla_config/opt | /fusezilla_config/fuseopt", context);
	xmlXPathObjectPtr hosts = xmlXPathEvalExpression((const xmlChar*) "/fusezilla_config/host", context);
	xmlNodePtr host = NULL;
	int i;
	for(i = 0; i < hosts->nodesetval->nodeNr; i++) {
		cur = hosts->nodesetval->nodeTab[i];
		const xmlChar *id = xmlGetProp(cur, (const xmlChar*) "id");
		const xmlChar *uri = xmlGetProp(cur, (const xmlChar*) "uri");
		if(key == FZ_HOST_ID && !xmlStrcmp(id, (const xmlChar*) value))
			host = cur;
		if(key == FZ_HOST_URI && !xmlStrcmp(uri, (const xmlChar*) value))
			host = cur;
		if(host)
			break;
	}
	if(!host)
		return(NULL);
	xmlNodePtr last = host->xmlChildrenNode;
	while(last->next && (last = last->next));
	for(i = 0; i < globalOpts->nodesetval->nodeNr; i++) {
		cur = globalOpts->nodesetval->nodeTab[i];
		cur = xmlCopyNode(cur, 2);
		cur->next = NULL;
		last->next = cur;
		last = cur;
	}
	xmlDocSetRootElement(result, host);
	this->host_config = result;
	return(result);
}

xmlDocPtr FSController::loadHostConfig(const char *spec_1) {
	char *spec = strdup(spec_1);
	char *assign = strchr(spec, '=');
	int op;
	if(assign)
		assign = '\0';
	if(assign && !strcmp(spec, "id"))
		op = FZ_HOST_ID;
	else if(assign && !strcmp(spec, "uri"))
		op = FZ_HOST_URI;
	else if(assign)
		return NULL;
	else
		op = FZ_HOST_URI;
	char *value = assign + 1;
	xmlDocPtr result = this->loadHostConfig(op, value);
	free(spec);
	return(result);
}

const char *FSController::getStringOpt(const char *opt_name) {
	xmlXPathContextPtr context = xmlXPathNewContext(this->host_config);
	xmlXPathObjectPtr host_opts = xmlXPathEvalExpression((const xmlChar *) "/host/opt", context);
	int i;
	xmlNodePtr cur = NULL;
	xmlChar *name, *value;
	for(i = 0; i < host_opts->nodesetval->nodeNr; i++) {
		cur = host_opts->nodesetval->nodeTab[i];
		name = xmlGetProp(cur, (const xmlChar *) "name");
		value = xmlGetProp(cur, (const xmlChar *) "value");
		if(!xmlStrcmp((const xmlChar *) opt_name, name))
			return((char *) value);
	}
	return(NULL);
}

const char *FSController::getHostURI() {
	xmlNodePtr root = xmlDocGetRootElement(this->host_config);
	return((const char *) xmlGetProp(root, (const xmlChar *) "uri"));
}

int FSController::main(int argc, char **argv, FSLayer *fs) {
	PtrList<FSLayer*> *ptr = new PtrList<FSLayer*>;
	ptr->value = fs;
	return main(argc, argv, ptr);
}

typedef FSLayer* (*module_initializer_p)();
int FSController::main(int argc, char **argv, PtrList<FSLayer*> *fs) {
	PtrList<FSLayer*> *cur = fs;
	/* Loading middleware */
	xmlXPathContextPtr context = xmlXPathNewContext(this->host_config);
	xmlXPathObjectPtr modifiers = xmlXPathEvalExpression((const xmlChar *) "/host/modifier", context);
	int i;
	for(i = modifiers->nodesetval->nodeNr - 1; i >= 0; i++) {
		cur = modifiers->nodesetval->nodeTab[i];
		src = xmlGetProp(cur, (const xmlChar *) "src");
		lh = dlopen(src, RTLD_LAZY);
		fun = dlsym(lh, "make_fslayer");
		module_initializer_p *init = reinterpret_cast<module_initializer_p*> fun;
		fs->push();
		fs->value = init();
	//	dlclose(lh);
	}
	/* Installing FSController callbacks to the layers */
	do { cur->value->controller = this; } while(cur = cur->next);
	/* Preparing FUSECompat for dealing with FSLayer */
	struct fuse_operations *oper = FUSECompat::init(fs);
	/* Passing control to fuse_main */
	return fuse_main(argc, argv, oper, NULL);
}

int FSController::error(const char *msg) {
	fprintf(stderr, "ERROR %s", msg);
//	if(errno)
//		fprintf(stderr, ": %s", strerror(errno));
	fprintf(stderr, "\n");
	return(-1);
}

#ifdef TEST
int main(int argc, char **argv) {
	// xmlDocPtr doc = fz_loadConfig(NULL);
	// xmlSaveFormatFile((const xmlChar*) "out.xml", doc, 1);
	xmlDocPtr doc = fz_getConfig(FZ_HOST_ID, "5f97");
	xmlSaveFormatFile((const xmlChar*) "out.xml", doc, 1);
}
#endif
